package com.shelpe.services.algorithm.development;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.shelpe.services.algorithm.client.SinbadClient;
import com.shelpe.services.algorithm.entity.batis.Keyword;
import com.shelpe.services.algorithm.entity.batis.KeywordExample;
import com.shelpe.services.algorithm.entity.jpa.Adgroup;
import com.shelpe.services.algorithm.entity.jpa.DevelopmentKeyword;
import com.shelpe.services.algorithm.entity.mongo.KeywordDevelopmentLog;
import com.shelpe.services.algorithm.repository.batis.KeywordMapper;
import com.shelpe.services.algorithm.repository.jpa.AdgroupRepository;
import com.shelpe.services.algorithm.repository.jpa.DevelopmentKeywordRepository;
import com.shelpe.services.algorithm.repository.jpa.DevelopmentPlanRepository;
import com.shelpe.services.algorithm.repository.mongo.KeywordDevelopmentLogRepository;
import com.shelpe.services.algorithm.service.RealtimeRankService;
import com.shelpe.services.algorithm.sustain.SustainRule;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class DevelopmentSustainRule implements SustainRule<DevelopmentSustainer> {

	@Autowired
	private DevelopmentPlanRepository developmentPlanRepository;
	@Autowired
	private DevelopmentKeywordRepository developmentKeywordRepository;
	@Autowired
	private KeywordMapper keywordMapper;
	@Autowired
	private KeywordDevelopmentSustainRule sustainRule;
	@Autowired
	private AdgroupRepository adgroupRepository;
	@Autowired
	private KeywordDevelopmentLogRepository keywordDevelopmentLogRepository;
	@Autowired
	private RealtimeRankService realtimeRankService;
	@Autowired
	private SinbadClient sinbadClient;
	
	@Override
	public boolean check(DevelopmentSustainer entity) {
		Date today = new Date();
		
		boolean between = true;
		if(entity.getPlan().getFromTime() != null && entity.getPlan().getToTime() != null){
			if(DateUtils.truncatedCompareTo(entity.getPlan().getFromTime(), today, Calendar.DATE) > 0 
					|| DateUtils.truncatedCompareTo(entity.getPlan().getToTime(), today, Calendar.DATE) < 0){
				between = false;
			}
		}
		log.debug("{} in expiry time: {}", between, entity.getPlan());
		
		boolean inPeriod = false;
		Date expectedTime = DateUtils.addMinutes(entity.getPlan().getModifiedTime(), entity.getPlan().getPeriod());
		if(DateUtils.truncatedCompareTo(expectedTime, new Date(), Calendar.SECOND) < 0 ){
			inPeriod = true;
		}
		log.debug("{} in executing period: {}", inPeriod, entity.getPlan());
		
		boolean next =  between && inPeriod;
		if(!next){
			log.info("need not execute, go to next: {}", entity.getPlan());
			entity.getPlan().setModifiedTime(new Date());
			entity.getPlan().setNextTime(DateUtils.addMinutes(entity.getPlan().getModifiedTime(), entity.getPlan().getPeriod()));
			this.developmentPlanRepository.save(entity.getPlan());
		}
		return next;
	}

	@Override
	public boolean prepare(DevelopmentSustainer entity) {
		List<DevelopmentKeyword> targets = this.developmentKeywordRepository.findByPlanID(entity.getPlan().getAutoID());
		entity.setTargets(targets);
		if(log.isDebugEnabled() && targets != null){
			log.debug("development targets: {}", ToStringBuilder.reflectionToString(targets));
		}
		if(entity.getTargets() == null || entity.getTargets().size() == 0){
			log.error("development target is not found: {}", entity.getPlan());
			return false;
		}
		
		List<Long> keywordIDs = new ArrayList<Long>();
		for(DevelopmentKeyword target: targets){
			keywordIDs.add(target.getKeywordID());
		}
		
		KeywordExample ex = new KeywordExample();
		ex.setUserId(entity.getPlan().getUserID());
		ex.createCriteria().andKeywordIdIn(keywordIDs);
		List<Keyword> keywords = this.keywordMapper.selectByExample(ex);
		entity.setKeywords(keywords);
		if(log.isDebugEnabled() && keywords != null){
			log.debug("keywords: {}", ToStringBuilder.reflectionToString(keywords));
		}
		if(entity.getKeywords() == null || entity.getKeywords().size() == 0){
			log.error("keywords is not found: {}", entity.getPlan());
			return false;
		}
		
		
		Adgroup adgroup = this.adgroupRepository.findOne(entity.getPlan().getContainerID());
//		Adgroup adgroup = this.adgroupRepository.findOne(1l);
		if(adgroup != null){
			log.debug("adgroup: {}", adgroup);
			entity.setNick(adgroup.getNick());
			entity.setProductID(adgroup.getProductID());
		}else{
			log.error("exit as adgroup is not found: {}", entity.getPlan());
			return false;
		}
		
		keywordIDs = new ArrayList<Long>();
		for(Keyword kw: entity.getKeywords()){
			keywordIDs.add(kw.getKeywordId());
		}
		entity.setRanks(this.realtimeRankService.syncRanks(adgroup.getNick(), adgroup.getAdgroupID(), keywordIDs));
		if(log.isDebugEnabled()){
			log.debug("ranks: {}", entity.getRanks());
		}
		
		return true;
	}

	@Override
	public SustainStatus sustain(DevelopmentSustainer entity) {
		
		List<Keyword> pricingKeywords = new ArrayList<Keyword>();
		List<KeywordDevelopmentLog> logs = new ArrayList<KeywordDevelopmentLog>();
		for(DevelopmentKeyword dk: entity.getTargets()){
			KeywordDevelopmentSustainer kSustainer = new KeywordDevelopmentSustainer();
			kSustainer.setNick(entity.getNick());
			kSustainer.setProductID(entity.getProductID());
			kSustainer.setPlan(entity.getPlan());
			kSustainer.setTarget(dk);
			kSustainer.setRank(entity.findRank(dk.getKeywordID()));
			Keyword word = null;
			for(Keyword kw: entity.getKeywords()){
				if(kw.getKeywordId() == dk.getKeywordID()){
					word = kw;
					break;
				}
			}
			kSustainer.setKeyword(word);
			
			boolean toNext = this.sustainRule.check(kSustainer);
			if(toNext){
				toNext = this.sustainRule.prepare(kSustainer);
				if(toNext){
					SustainRule.SustainStatus ss = this.sustainRule.sustain(kSustainer);
					toNext = this.sustainRule.maintain(kSustainer, ss);
					if(ss == SustainRule.SustainStatus.pricing || ss == SustainRule.SustainStatus.match
							 || ss == SustainRule.SustainStatus.raising  || ss == SustainRule.SustainStatus.reducing){
						pricingKeywords.add(kSustainer.getKeyword());
						logs.add(kSustainer.getLog());
					}
					if(toNext){
						toNext = this.sustainRule.mark(kSustainer);
					}
				}
			}
			log.debug("sustain result - {} : {}", toNext, dk);
		}
		
		
		if(pricingKeywords.size() > 0){
			List<SinbadClient.PricingKeyword> handlings = new ArrayList<SinbadClient.PricingKeyword>();
			for(Keyword kw: pricingKeywords){
				SinbadClient.PricingKeyword hkw = new SinbadClient.PricingKeyword();
				hkw.setKeywordID(kw.getKeywordId());
				hkw.setIsDefaultPrice(0);
				hkw.setMatchScope(kw.getMatchscope());
				hkw.setMaxPrice(kw.getMaxPrice());
				hkw.setMaxMobilePrice(kw.getMaxMobilePrice());
				hkw.setMobileIsDefaultPrice(0);
				handlings.add(hkw);
				log.debug("to pricing: {}", hkw);
			}
			
			SinbadClient.ListResponse<SinbadClient.PricingKeyword> ret = this.sinbadClient.pricingKeywords(entity.getPlan().getUserID(), handlings);
			log.debug("price keywords ret: {}", ret);
			if(ret.getDatas() != null && ret.getDatas().size() > 0){
				if(handlings.size() != ret.getDatas().size()){
					log.error("some keywords fail to priced: {}", entity.getPlan());
				}
				for(SinbadClient.PricingKeyword pk:ret.getDatas()){
					KeywordDevelopmentLog log = this.filterByKeywordID(logs, pk.getKeywordID());
					if(log != null){
						log.setResult(true);
					}
				}
			}else{
				log.error("no keyword is success to priced: {}", entity.getPlan());
			}
			this.keywordDevelopmentLogRepository.save(logs);
		}
		
		return SustainStatus.keep;
	}
	
	private KeywordDevelopmentLog filterByKeywordID(List<KeywordDevelopmentLog> logs, long keywordID){
		for(KeywordDevelopmentLog log: logs){
			if(log.getKeywordID() == keywordID){
				return log;
			}
		}
		return null;
	}

	@Override
	public boolean maintain(DevelopmentSustainer entity,
			SustainStatus status) {
		
		return true;
	}

	@Override
	public boolean mark(DevelopmentSustainer entity) {
		entity.getPlan().setExecutions(entity.getPlan().getExecutions() + 1);
		entity.getPlan().setModifiedTime(new Date());
		entity.getPlan().setNextTime(DateUtils.addMinutes(entity.getPlan().getModifiedTime(), entity.getPlan().getPeriod()));
		this.developmentPlanRepository.save(entity.getPlan());
		return true;
	}
	
}
